package com.zk.config.api.client;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooKeeper;
import com.zk.config.api.service.ZkCache;
import com.zk.config.api.util.ZkCom;
import lombok.Getter;
import lombok.Setter;

public class ConfigClient {
	private static Logger logger = Logger.getLogger(ConfigClient.class);
	
	public static Map<String, byte[]> fileValue = new ConcurrentHashMap<>();
	public static Properties properties;
	public static Map<String, Properties> fileProperties = new ConcurrentHashMap<>();
	public static Map<String, List<String>> nodeChilds = new ConcurrentHashMap<>();
	
	@Getter@Setter
	private int connectTimeout = 5000;
	@Getter@Setter
	private String zkHost = "";
	@Getter@Setter
	private long refreshDataTime = 0;
	@Getter
	private boolean watch = false;
	@Getter
	private String authInfo = "";
	@Getter
	private String log4jPathName = "log4j.properties";
	@Getter
	private String mapingToLocalTag = ":";
	// Constants.class.getResource("/").getPath();
	@Getter
	private String mapingDir = Thread.currentThread().getContextClassLoader().getResource("").getPath();
	
	private Watcher watcher;
	@Getter
	private boolean isClose = true;
	@Getter
	public ZkCache zkCache = new ZkCache();
	
	private TimeThread timeThread;
	
	public void setWatch(boolean watch) {
		this.watch = watch;
		zkCache.setWatch(watch);
	}
	
	public void setAuthInfo(String authInfo) {
		this.authInfo = authInfo;
		zkCache.setAuthInfo(authInfo);
	}
	
	public void setLog4jPathName(String log4jPathName){
		this.log4jPathName = log4jPathName;
		zkCache.setLog4jPathName(log4jPathName);
	}
	
	public void setMapingToLocalTag(String mapingToLocalTag){
		this.mapingToLocalTag = mapingToLocalTag;
		zkCache.setMapingToLocalTag(mapingToLocalTag);
	}
	
	public void setMapingDir(String mapingDir){
		this.mapingDir = mapingDir;
		zkCache.setMapingDir(mapingDir);
	}
	
	public ConfigClient(String zkHost) {
		this(zkHost, 5000, 0, false, null, null);
	}
	
	public ConfigClient(String zkHost, int connectTimeout) {
		this(zkHost, connectTimeout, 0, false, null, null);
	}
	
	public ConfigClient(String zkHost, int connectTimeout, Properties properties) {
		this(zkHost, connectTimeout, 0, false, properties, null);
	}
	
	public ConfigClient(String zkHost, int connectTimeout, long refreshDataTime, boolean watch) {
		this(zkHost, connectTimeout, refreshDataTime, watch, null, null);
	}
	
	public ConfigClient(String zkHost, int connectTimeout, long refreshDataTime, boolean watch, Properties properties) {
		this(zkHost, connectTimeout, refreshDataTime, watch, properties, null);
	}
	
	public ConfigClient(String zkHost, int connectTimeout, long refreshDataTime, boolean watch, Properties properties, String authInfo) {
		this.zkHost = zkHost;
		this.connectTimeout = connectTimeout;
		this.refreshDataTime = refreshDataTime;
		this.watch = watch;
		this.authInfo = authInfo;
		if(ConfigClient.properties == null) {
			ConfigClient.properties=properties;
		}
		zkCache.setWatch(watch);
		zkCache.setAuthInfo(authInfo);
		zkCache.setLog4jPathName(authInfo);
		zkCache.setMapingDir(getMapingDir());
		zkCache.setMapingToLocalTag(getMapingToLocalTag());
		if (this.watch) {
			this.watcher = new Watcher() {
				@Override
				public void process(WatchedEvent event) {
					 if (event.getState() == KeeperState.Expired) {
						 if (zkCache.getZk() != null) {
							 if(!zkCache.getZk().getState().isConnected()) {
								 try {
									 zkCache.getZk().close();
								 } catch (InterruptedException e) {
									 e.printStackTrace();
								 }
								 isClose = true;
								 zkCache.setZk(null);
								 getZk();
							 }
						 } else {
							 getZk();
						 }
					 }
					String path = event.getPath();
					if (event.getType() != EventType.None) {
						try {
							if(event.getType() == EventType.NodeDeleted) {
								zkCache.reloadIfNodeDelete(path);
							} else if (event.getType() == EventType.NodeCreated || event.getType() == EventType.NodeDataChanged) {
								try {
									zkCache.getZk().exists(path, true); // 回调继续监听监听变更的路径节点
								} catch (KeeperException | InterruptedException e) {
									e.printStackTrace();
									logger.error(e.getMessage());
									// 失败重试
									Thread.sleep(200);
									zkCache.getZk().exists(path, true);
								}
								zkCache.reloadIfNodeCreateOrChange(path);
							} else if(event.getType() == EventType.NodeChildrenChanged) {
								zkCache.read(path, true);
							}
						} catch (Exception e) {
							e.printStackTrace();
							logger.error(e.getMessage());
							zkCache.read(path, true);
						}
					}
				}
			};
		}
	}
	
	public ZooKeeper getZk() {
		if(zkCache.getZk() == null) {
			synchronized (ConfigClient.class) {
				try {
					String newZkHost = this.zkHost.replaceAll("/+", "/").replaceAll("/$", "");
					zkCache.setZk(new ZooKeeper(newZkHost, this.connectTimeout, watcher));
					if(this.authInfo != null && this.authInfo.length() > 0) {
						zkCache.getZk().addAuthInfo("digest", this.authInfo.getBytes());
					}
					isClose = false;
					zkCache.init();
				} catch (IOException e) {
					e.printStackTrace();
					logger.error(e.getMessage());
				}
			}
		}
		return zkCache.getZk();
	}
	
	public void close(){
		try {
			if (zkCache.getZk() != null) {
				zkCache.getZk().close();
			}
			isClose = true;
			if (timeThread != null) {
				timeThread.close();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			logger.error(e.getMessage());
		}
	}
	
	public void init() {
		getZk();
		if(this.refreshDataTime > 0) {
			timeThread = new TimeThread(this.refreshDataTime);
			timeThread.start();
		}
	}
	
	public static List<String> getChildren(String ...nodePath){
		return ConfigClient.nodeChilds.get(ZkCom.getZkPath(nodePath));
	}
	
	public static boolean hasChildren(String ...nodePath) {
		return ConfigClient.nodeChilds.containsKey(ZkCom.getZkPath(nodePath));
	}
	
	public static String getPropertiesValue(String key){
		if (ConfigClient.properties != null && ConfigClient.properties.size() > 0) {
			return ConfigClient.properties.getProperty(key);
		}
		return null;
	}
	
	public static Properties getPropertiesByNodePath(String ...nodePath){
		if (ConfigClient.fileProperties.size() > 0) {
			return ConfigClient.fileProperties.get(ZkCom.getZkPath(nodePath));
		}
		return null;
	}
	
	public static byte[] getFileValueByNodePath(String ...nodePath){
		if (ConfigClient.fileValue.size() > 0) {
			return ConfigClient.fileValue.get(ZkCom.getZkPath(nodePath));
		}
		return null;
	}
	
	class TimeThread extends Thread {
		private long time;
		private boolean runflag = true;
		public TimeThread(long time) {
			this.time = time;
		}
		public void run() {
			while(runflag) {
				try {
					Thread.sleep(time);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if(runflag) {
					zkCache.refresh();
				}
			}
		}
		public void close(){
			runflag = false;
			this.interrupt();
		}
	}
}
